﻿namespace Home.Mvc.Controllers
{
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Transactions;
    using System.Web;
    using System.Web.Mvc;
    using CsvHelper;
    using CsvHelper.Configuration;
    using Home.Mvc.Models;
    using Home.Services.ProductService;

    [ClientMvcAuthorize]
    public class ProductController : Controller
    {
        [HttpPost]
        [ProductUploadHandleError(RunningHtmlId = "diamond-import-running", SuccessHtmlId = "diamond-import-success", FailureHtmlId = "diamond-import-failure")]
        public async Task<ActionResult> UploadDiamondFile(HttpPostedFileBase file, ImportType type)
        {
            if (file == null || file.ContentLength == 0 || !this.ModelState.IsValid)
            {
                return new HttpStatusCodeResult(HttpStatusCode.PreconditionFailed);
            }

            var transaction = default(TransactionScope);
            var database = default(ProductDbContext);
            var streamReader = default(StreamReader);
            var csvReader = default(CsvReader);
            try
            {
                streamReader = new StreamReader(file.InputStream);
                csvReader = new CsvReader(streamReader, new CsvConfiguration()
                {
                    Delimiter = ",",
                    HasHeaderRecord = true,
                    IgnoreHeaderWhiteSpace = false,
                    Quote = '\"',
                    QuoteAllFields = false,
                    QuoteNoFields = false,
                    ThrowOnBadData = true,
                    TrimFields = true,
                    Encoding = Encoding.Unicode,
                });
                transaction = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted }, TransactionScopeAsyncFlowOption.Enabled);
                database = new ProductDbContext();

                var now = DateTimeOffset.UtcNow;
                var user = Thread.CurrentPrincipal.Identity.Name;

                var import = new DiamondImport()
                {
                    Id = Guid.NewGuid(),
                    Type = type,
                    Created = now,
                    CreatedBy = user,
                    Modified = now,
                    ModifiedBy = user,
                };
                database.DiamondImports.Add(import);

                var count = 0;
                switch (type)
                {
                    case ImportType.Append:
                        {
                            while (csvReader.Read())
                            {
                                var diamond = New(csvReader, import, now, user);
                                diamond.Id = Guid.NewGuid();
                                database.Diamonds.Add(diamond);
                                ++count;
                            }
                        }
                        break;
                    case ImportType.Replace:
                        {
                            await database.Database.ExecuteSqlCommandAsync("DELETE tc_product_diamond");
                            while (csvReader.Read())
                            {
                                var diamond = New(csvReader, import, now, user);
                                diamond.Id = Guid.NewGuid();
                                database.Diamonds.Add(diamond);
                                ++count;
                            }
                        }
                        break;
                    case ImportType.Update:
                        {
                            var diamonds = await database.Diamonds.ToArrayAsync();
                            while (csvReader.Read())
                            {
                                var index = csvReader.GetField<int>(0);
                                var diamond = diamonds.FirstOrDefault(d => d.Index == index);
                                if (diamond == null)
                                {
                                    throw new InvalidOperationException("The diamond with index '{0}' does not exist.");
                                }
                                SetValues(csvReader, diamond, import, now, user, true);
                                ++count;
                            }
                        }
                        break;
                    case ImportType.AppendOrUpdate:
                        {
                            var diamonds = await database.Diamonds.ToArrayAsync();
                            while (csvReader.Read())
                            {
                                var index = csvReader.GetField<int>(0);
                                var diamond = diamonds.FirstOrDefault(d => d.Index == index);
                                if (diamond == null)
                                {
                                    diamond = New(csvReader, import, now, user);
                                    diamond.Id = Guid.NewGuid();
                                    database.Diamonds.Add(diamond);
                                }
                                else
                                {
                                    SetValues(csvReader, diamond, import, now, user, true);
                                }
                                ++count;
                            }
                            break;
                        }
                }

                import.Count = count;
                await database.SaveChangesAsync();
                transaction.Complete();
            }
            finally
            {
                if (csvReader != null) csvReader.Dispose();
                if (streamReader != null) streamReader.Dispose();
                if (database != null) database.Dispose();
                if (transaction != null) transaction.Dispose();
            }

            return this.Content(ProductUploadHelper.BuildResponseHtml(true, "diamond-import-running", "diamond-import-success", "diamond-import-failure"), "text/html", Encoding.UTF8);
        }

        private static string GetString(CsvReader reader, int index)
        {
            var value = reader.GetField<string>(index);
            return string.IsNullOrEmpty(value) ? default(string) : value;
        }

        private static T? GetEnum<T>(CsvReader reader, int index)
            where T : struct
        {
            var value = reader.GetField<string>(index);
            return string.IsNullOrEmpty(value) ? new T?() : new T?((T)Enum.Parse(typeof(T), value));
        }

        private static decimal? GetDecimal(CsvReader reader, int index)
        {
            var value = reader.GetField<string>(index);
            return string.IsNullOrEmpty(value) ? new decimal?() : new decimal?(decimal.Parse(value));
        }

        private static string GetStringLast(CsvReader reader, int index)
        {
            var value = default(string);
            if (reader.TryGetField<string>(index, out value))
            {
                return string.IsNullOrEmpty(value) ? default(string) : value;
            }
            return null;
        }

        private static Diamond New(CsvReader reader, DiamondImport import, DateTimeOffset now, string user)
        {
            var diamond = new Diamond();
            SetValues(reader, diamond, import, now, user, false);
            return diamond;
        }

        private static void SetValues(CsvReader reader, Diamond diamond, DiamondImport import, DateTimeOffset now, string user, bool update)
        {
            if (!update)
            {
                diamond.Created = now;
                diamond.CreatedBy = user;
                diamond.Status = ProductStatus.Active;
            }

            var pos = 0;
            diamond.Modified = now;
            diamond.ModifiedBy = user;
            diamond.Index = reader.GetField<int>(pos++);
            diamond.Type = GetEnum<DiamondType>(reader, pos++).Value;
            diamond.Shape = GetEnum<DiamondShape>(reader, pos++);
            diamond.Cost = reader.GetField<decimal>(pos++);
            diamond.SalePrice = reader.GetField<decimal>(pos++);
            diamond.Vendor = GetString(reader, pos++);
            diamond.ImportId = import.Id;
            diamond.ReportType = GetEnum<DiamondReport>(reader, pos++);
            diamond.ReportNumber = GetString(reader, pos++);
            diamond.Caret = GetDecimal(reader, pos++);
            diamond.Clarity = GetEnum<DiamondClarity>(reader, pos++);
            diamond.Color = GetEnum<DiamondColor>(reader, pos++);
            diamond.ColoredColor = GetString(reader, pos++);
            diamond.Cut = GetEnum<DiamondCut>(reader, pos++);
            diamond.Polish = GetEnum<DiamondPolish>(reader, pos++);
            diamond.Symmetry = GetEnum<DiamondSymmetry>(reader, pos++);
            diamond.Fluorescence = GetString(reader, pos++);
            diamond.Comment = GetStringLast(reader, pos++);
        }
    }
}